/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/
const PENDING = 'pending'
const FULFILLING = 'fulfilling'
const REJECTED = 'rejected'
class MyPromise {
  constructor(executor) {
    this.state = PENDING
    this.value = undefined
    this.reason = undefined
    this.onResolvedCallbacks = []
    this.onRejectedCallbacks = []
    try {
      executor(this.resolve.bind(this), this.reject.bind(this)) // 绑定this，让resolve和reject始终指向当前实例
    } catch (e) {
      this.reject.call(this, e)
    }
  }
  resolve(value) {
    if (this.state === PENDING) {
      this.state = FULFILLING // 改变状态为成功，并且不让改变
      this.value = value // 保存成功结果
      while (this.onResolvedCallbacks.length) this.onResolvedCallbacks.shift()() // 当有链式调用多个then时，不止一个回调
    }
  }
  reject(reason) {
    if (this.state === PENDING) {
      this.state = REJECTED // 改变状态为失败，并且不让改变
      this.reason = reason // 保存失败结果
      while (this.onRejectedCallbacks.length) this.onRejectedCallbacks.shift()() // 当有链式调用多个then时，不止一个回调
    }
  }
  then(onResolvedCallback, onRejectedCallback) {
    // 当then中传入的不是函数时，直接返回传入的内容
    onResolvedCallback = onResolvedCallback ? onResolvedCallback : value => value
    onRejectedCallback = onRejectedCallback ? onRejectedCallback : reason => reason
    let promise2 = new Promise((resolve, reject) => {
      // 使用定时器是为了获取到promise2，因为JS是从右往左运行，所以调用promise2时，promise2还没被赋值
      if (this.state === FULFILLING) {
        setTimeout(() => {
          try {
            let result = onResolvedCallback(this.value)
            resolvePromise(promise2, result, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      } else if (this.state === REJECTED) {
        setTimeout(() => {
          try {
            let result = onRejectedCallback(this.reason)
            resolvePromise(promise2, result, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      } else {
        // 解决异步时还没处理完结果就已经被then返回结果的问题
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let result = onResolvedCallback(this.value)
              resolvePromise(promise2, result, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let result = onRejectedCallback(this.reason)
              resolvePromise(promise2, result, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
      }
    })
    return promise2
  }
  finally(callback) {
    return this.then(
      result => {
        MyPromise.resolve(callback())
          .then(resolve => resolve(result))
      },
      error => {
        MyPromise.resolve(callback())
          .then(resolve => resolve(error))
      }
    )
  }
  catch(callback) {
    return this.then(undefined, callback)
  }
  static all(array) {
    return new Promise((resolve, reject) => {
      let results = []
      let index = 0
      function addData(key, value) {
        results[key] = value
        index++
        if (index == results.length - 1) {
          resolve(results)
        }
      }
      for (let i = 0; i < array.length; i++) {
        if (array[i] instanceof MyPromise) {
          array[i].then(
            res => {
              addData(i, res)
            },
            err => {
              reject(err)
            }
          )
        } else {
          addData(i, array[i])
        }
      }
    })
  }
  static race(array) {
    return new Promise((resolve, reject) => {
      for (let i = 0; i < array.length; i++) {
        if (array[i] instanceof MyPromise) {
          array[i].then(
            res => {
              resolve(res)
            },
            err => {
              reject(err)
            }
          )
        } else {
          resolve(array[i])
        }
      }
    })
  }
  static resolve(value) {
    if (value instanceof MyPromise) {
      return value
    } else {
      return new MyPromise((resolve, reject) => resolve(value))
    }
  }
  static reject(value) {
    if (value instanceof MyPromise) {
      return value
    } else {
      return new MyPromise((resolve, reject) => reject(value))
    }
  }
}
function resolvePromise(promise2, result, resolve, reject) {
  if (promise2 === result) return reject(new TypeError('不能在then中调用自身的promise对象'))
  if (result instanceof MyPromise) {
    result.then(
      res => {
        resolve(res)
      },
      err => {
        reject(err)
      }
    )
  } else {
    resolve(result)
  }
}